<?php

namespace ASW\Utility\HttpClient;


use Exception;
use function curl_init;
use function curl_setopt;
use function http_build_query;
use function parse_str;
use function parse_url;

class Request
{
    private string          $_url              = '';
    private RequestMethod   $_method           = RequestMethod::GET;
    private array           $_headers          = [];
    private array           $_cookies          = [];
    private string          $_proxyAddress     = '';
    private string          $_auth             = '';
    private string          $_userAgent        = 'ASW.HttpClient';
    private int             $_connectTimeout   = 10;
    private int             $_receiveTimeout   = 30;
    private bool            $_followRedirect   = true;
    private bool            $_useCompress      = false;
    private mixed           $_progressCallback = null;
    private ?RequestContent $_content          = null;

    public function __construct(string $url)
    {
        if (!extension_loaded('curl')) {
            throw new Exception('需要加载 curl 扩展');
        }
        $this->_url = $url;
    }

    public function setProgressCallback(callable $callback): static
    {
        $this->_progressCallback = $callback;

        return $this;
    }

    public function cookie(string $key, string $value): static
    {
        $this->_cookies[$key] = $value;

        return $this;
    }

    public function useCompress($enable = true): static
    {
        $this->_useCompress = $enable;

        return $this;
    }

    public function followRedirect(bool $enabled = true): static
    {
        $this->_followRedirect = $enabled;

        return $this;
    }

    public function connectTimeout(int $seconds): static
    {
        $this->_connectTimeout = $seconds;

        return $this;
    }

    public function receiveTimeout(int $seconds): static
    {
        if ($seconds > 0) $this->_receiveTimeout = $seconds;

        return $this;
    }

    public function auth(string $userName, string $password): static
    {
        $this->_auth = "$userName:$password";

        return $this;
    }

    public function proxy(string $proxyAddress): static
    {
        $this->_proxyAddress = $proxyAddress;

        return $this;
    }

    public function accept(string $accept): static
    {
        $this->header('Accept', $accept);

        return $this;
    }

    public function header(string $key, string $value, bool $replace = false): static
    {
        if ($replace || !array_key_exists($key, $this->_headers)) $this->_headers[$key] = [];
        $this->_headers[$key][] = $value;

        return $this;
    }

    public function acceptLanguage(string $acceptLanguage): static
    {
        $this->header('Accept-Language', $acceptLanguage);

        return $this;
    }

    public function dnt(bool $enabled = true): static
    {
        $this->header('Dnt', $enabled ? 1 : 0);

        return $this;
    }

    public function noCache(): static
    {
        $this->header('Pragma', 'no-cache');
        $this->header('Cache-Control', 'no-cache');

        return $this;
    }

    public function referer(string $referer): static
    {
        $this->header('Referer', $referer);

        return $this;
    }

    public function userAgent(string $userAgent): static
    {
        $this->_userAgent = $userAgent;

        return $this;
    }

    public function get(): Response
    {
        $this->method(RequestMethod::GET);

        return $this->request();
    }

    public function method(RequestMethod $method): static
    {
        $this->_method = $method;

        return $this;
    }

    public function request(): Response
    {
        $urlInfo = parse_url($this->_url);
        if (isset($urlInfo['query'])) {
            parse_str($urlInfo['query'], $queryArray);
            // $urlInfo['query'] = \http_build_query($queryArray);
            $this->_url = preg_replace("/\?[^#]*/", '?' . http_build_query($queryArray), $this->_url);
        }

        $curl = curl_init();
        curl_setopt($curl, CURLOPT_URL, $this->_url);
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);  // exec 时， 以文本流返回, 若设置为0则自动输出内容， 同时返回true
        curl_setopt($curl, CURLOPT_HEADER, 1);          // 启用时会将头文件的信息作为数据流输出。
        curl_setopt($curl, CURLOPT_CONNECTTIMEOUT, $this->_connectTimeout);
        curl_setopt($curl, CURLOPT_TIMEOUT, $this->_receiveTimeout);
        curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false); // 规避 SSL 证书检查
        curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, false); // 不验证 SSL 证书域名
        curl_setopt($curl, CURLOPT_AUTOREFERER, true);     // 当根据Location:重定向时，自动设置header中的Referer:信息

        if ($this->_followRedirect) {
            curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 1);
            // 指定最多的HTTP重定向的数量
            curl_setopt($curl, CURLOPT_MAXREDIRS, 30);
        }

        if (count($this->_cookies) > 0) {
            $allCookieItems = [];
            foreach ($this->_cookies as $key => $value) {
                $allCookieItems[] = "$key=$value";
            }
            $allCookieValue = implode('; ', $allCookieItems);
            curl_setopt($curl, CURLOPT_COOKIE, $allCookieValue);
        }

        if ($this->_useCompress) curl_setopt($curl, CURLOPT_ENCODING, 'gzip, deflate');
        if (!empty($this->_userAgent)) curl_setopt($curl, CURLOPT_USERAGENT, $this->_userAgent);

        if ($this->_method !== RequestMethod::GET) {
            curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $this->_method->value);

            if ($this->_content !== null && ($this->_method === RequestMethod::POST || $this->_method === RequestMethod::PUT)) {
                curl_setopt($curl, CURLOPT_POSTFIELDS, $this->_content->getContent());
                $this->header('Content-Type', $this->_content->getContentType());
            }
        }


        if (!empty($this->_auth)) {
            curl_setopt($curl, CURLOPT_HTTPAUTH, CURLAUTH_BASIC); // 身份认证模式
            curl_setopt($curl, CURLOPT_USERPWD, $this->_auth);
        }

        if (!empty($this->_proxyAddress)) {
            $proxyInfo = parse_url($this->_proxyAddress);
            if (isset($proxyInfo['host']) && isset($proxyInfo['port'])) {
                curl_setopt($curl, CURLOPT_PROXY, $proxyInfo['host']);     // 代理服务器地址
                curl_setopt($curl, CURLOPT_PROXYPORT, $proxyInfo['port']); // 代理服务器端口

                $schemeMap = [
                    // 'http'   => CURLPROXY_HTTP,
                    'socks'  => CURLPROXY_SOCKS5,
                    'socks4' => CURLPROXY_SOCKS4,
                    'socks5' => CURLPROXY_SOCKS5,
                ];

                if (array_key_exists($proxyInfo['scheme'], $schemeMap)) {
                    curl_setopt($curl, CURLOPT_PROXYTYPE, $schemeMap[$proxyInfo['scheme']]); // 代理类型
                }

                if (isset($proxyInfo['user']) && isset($proxyInfo['pass'])) {
                    curl_setopt($curl, CURLOPT_PROXYAUTH, CURLAUTH_BASIC);                                 // 代理认证模式
                    curl_setopt($curl, CURLOPT_PROXYUSERPWD, "{$proxyInfo['user']}:{$proxyInfo['pass']}"); // 代理认证
                }
            } elseif (isset($proxy['skip'])) {
                curl_setopt($curl, CURLOPT_NOPROXY, 1); // 跳过代理
            }
        }

        if ($this->_progressCallback !== null) {
            curl_setopt($curl, CURLOPT_NOPROGRESS, false);
            curl_setopt($curl, CURLOPT_PROGRESSFUNCTION, $this->_progressCallback);
        }

        $header = [//'Host' => parse_url($this->_url, PHP_URL_HOST)
        ];
        foreach ($this->_headers as $key => $value) {
            $uCaseKey = str_replace(' ', '-', ucwords(str_replace('-', ' ', $key)));
            if (is_array($value)) {
                foreach ($value as $subValue) {
                    $header[] = "$uCaseKey: $subValue";
                }
            } else {
                $header[] = "$uCaseKey: $value";
            }
        }
        curl_setopt($curl, CURLOPT_HTTPHEADER, $header);

        $responseStream = curl_exec($curl);
        $responseInfo   = curl_getinfo($curl);

        return Response::fromCurlResponse($responseStream, ResponseInfo::fromCurlResponseInfo($responseInfo));
    }

    public function post(RequestContent $data = null): Response
    {
        $this->_content = $data;
        $this->method(RequestMethod::POST);

        return $this->request();
    }

    public function put(RequestContent $data = null): Response
    {
        $this->_content = $data;
        $this->method(RequestMethod::PUT);

        return $this->request();
    }

    /*
    public function download(string $saveDir, string $saveFileName = '') : Response
    {
        $this->method(RequestMethod::GET);

        return $this->request();
    }
    */

    public function delete(): Response
    {
        $this->method(RequestMethod::DELETE);

        return $this->request();
    }
}